<?php
/**
 * Base model class. Provides functionality to get list of records, build pagings, delete & edit record.
 * Uses Zend_Db_Table_Adstract class as parent. 
 */
abstract class WZ_Model_Abstract extends Zend_Db_Table_Abstract 
{
	/**
	 * DB adapter object
	 *
	 * @var Zend_Db_Adapter_Abstract
	 */
	protected $_db;
	
	/**
	 * Zend_Db_Select object
	 *
	 * @var Zend_Db_Select
	 */
	private $_select;
	
	/**
	 * Table name
	 *
	 * @var string
	 */
	protected $_name;
	
	/**
	 * Paging object
	 *
	 * @var Zend_Paginator
	 */
	private $_paging;
	
	/**
	 * Quantity of items to show on page. If 0 then all 
	 * records will be return
	 *
	 * @var int
	 */
	protected $_items_on_page = 0;
	
	/**
	 * Defines name of method to generate select query for getAll()
	 *
	 * @var string
	 */
	private $_selectMethod = '_selectQuery';/**
	 * Cache memory
	 * @var array
	 */
	private $cachedMethods = array();
	
	/**
	 * Is cache enabled ?
	 * @var bool
	 */
	private $cacheEnabled = false;
	
	/**
	 * Constructor.
	 *
	 * Supported params for $config are:
	 * - db              = user-supplied instance of database connector,
	 *                     or key name of registry instance.
	 * - name            = table name.
	 * - primary         = string or array of primary key(s).
	 * - rowClass        = row class name.
	 * - rowsetClass     = rowset class name.
	 * - referenceMap    = array structure to declare relationship
	 *                     to parent tables.
	 * - dependentTables = array of child tables.
	 * - metadataCache   = cache for information from adapter describeTable().
	 *
	 * @param  mixed $config Array of user-specified config options, or just the Db Adapter.
	 * @return void
	 */	
	public function __construct($array = array()) 
    {
		$this->_db = Zend_Registry::get ( 'ZendDb' );
		self::setDefaultAdapter ( $this->_db );
		parent::__construct ( $array );
	}
		
	/**
	 * Method to get data from a table.
	 *
	 * @param string $where - where clause
	 * @param string $order - order clause
	 * @param int $page - page number to select
	 * @return array - array with stdObjects
	 */
	public function getAll($where = null, $order = null, $page = 1)
    {
		 $select = $this->{$this->_selectMethod}();//_selectQuery();
		 
		 if($where){
		 	$select->where($where);		 	
		 }
		 
		 if($order){
		 	$select->reset('order')->order($order);		 	
		 }	
		 
		 $dataset = null;
		 
		 if($this->_items_on_page){
		 	$this->_setPaging($select, $page);
		 	$dataset = $this->_getAllByPage($page);	
		 }else{
		 	$dataset = $this->_db->query($select)->fetchAll();
		 }
		 
		 return $dataset;
	}
	
	/**
	 * Sets name of the method to generate select query for getAll()
	 *
	 * @param string $methodName
	 */
	public function setSelectMethod($methodName)
    {
		$this->_selectMethod = $methodName;
	}
	
	/**
	 * Returns entry by primary key value
	 *
	 * @param integer $id - primary key value
	 * @return Zend_Db_Table_Row | false
	 */
	public function getEntryById($id) 
    {
		if (($row = $this->find ( $id )->current ())) {
			return $row;
		} else {
			return false;
		}
	}
	
	/**
	 * Returns paging object
	 *
	 * @param string $url - url to use in paging control
	 * @return array
	 */
	public function getPaging($url = null)
    {
		if($this->_paging){
			$paging = $this->_paging->getPages();
			$paging->url = $url;
			return $paging;
		}
		return null;
	} 
	
	/**
	 * Set quantity of items to return after getAll method
	 *
	 * @param int $items_on_page
	 */
	public function setItemsOnPage($items_on_page)
    {
		$this->_items_on_page = $items_on_page;
	}
	
	/**
	 * Get quantity of items to return after getAll method
	 *
	 * @return  int $items_on_page
	 */
	public function getItemsOnPage()
    {
		return $this->_items_on_page;
	}
			
	/**
	 * Return table's primary key field name
	 *
	 * @return string
	 */
	public function getIdCol() 
    {
		if (isset ( $this->_primary [0] )) 
{
			return $this->_primary [0];
		} elseif (isset ( $this->_primary [1] )) {
			return $this->_primary [1];
		}	
	}
		
	/**
	 * Adds entry into a db
	 *
	 * @param array $data_to_add - associative array (key - name of a table field, value - field value)
	 * @return int - last inserted entry primary key
	 */
	public function addEntry($data_to_add) 
    {
		$row = $this->createRow ( $data_to_add );
		return $row->save ();
	}
		
	/**
	 * Deletes entry by primary key value
	 *
	 * @param int $id - primary key value
	 * @return int - the number of rows deleted.
	 */
	public function deleteEntryById($id) 
    {
		return $this->find ( $id )->current ()->delete ();
	}	
	
	/**
	 * Deletes entries by array of primary keys values
	 *
	 * @param array $ids - array of ints
	 * @return int - number of deleted rows
	 */
	public function bulkDeleteById($ids)
    {
		if(count($ids)){
			return $this->delete($this->getIdCol() . ' IN (' . implode(',', $ids) . ')');
		}
		return 0;
	}
	
	/**
	 * Updates an entry
	 *
	 * @param array $request - associative array (key - name of a table field, value - field value)
	 * @return int - number of affected rows
	 */
	public function updateEntry($request) 
    {
		$prep_request = $this->_prepareRequest ( $request );
		return $this->update ( $prep_request ['data'], $prep_request ['where'] );
	}
	
	/**
	 * Is used in getAll method. Returns select query. Override this method 
	 * in ancestor class to set another query
	 *
	 * @return Zend_Db_Select object
	 */
	protected function _selectQuery()
    {
		$select = $this->_select = $this->_db->select()->from($this->_name);
		return $select;
	}
	
	/**
	 * Initialize Zend_Paginator. Is user in getAll method
	 *
	 * @param Zend_Db_Select $select
	 * @param int $page - curreny page to return data from
	 */
	private function _setPaging(Zend_Db_Select $select, $page)
    {
		$this->_paging = new Zend_Paginator(new Zend_Paginator_Adapter_DbSelect($select));
		$this->_paging->setCurrentPageNumber($page);
		$this->_paging->setItemCountPerPage($this->_items_on_page);
	}
	
	/**
	 * Return data set according to current page numper. Is used in getAll method
	 *
	 * @param int $page - current page number
	 * @return array
	 */
	private function _getAllByPage($page)
    {
		return $this->_paging->getItemsByPage($page);
	}
	
	/**
	 * Gets formatted info about table
	 *
	 * @return array - info about table
	 */
	private function _getTableInfo() 
    {
		$tabledata ['cols'] = $this->info ( 'cols' );
		
		$index = array_search ( $this->_primary [1], $tabledata ['cols'] );
		unset ( $tabledata ['cols'] [$index] );
		return $tabledata;
	}
		
	/**
	 * Prepares request for entry update
	 *
	 * @param array $request - associative array (key - name of a table field, value - field value)
	 * @return array - prepared request 
	 */
	protected function _prepareRequest($request) 
    {
		$tabledata = $this->_getTableInfo ();
		foreach ( $tabledata ['cols'] as $col ) {
			$data [$col] = (isset ( $request [$col] )) ? $request [$col] : new Zend_Db_Expr ( $col );
		}
		$where = $this->getAdapter ()->quoteInto ( $this->_primary [1] . ' = ?', $request [$this->_primary [1]] );
		return array ('data' => $data, 'where' => $where );
	}
    
    public function fetchArray($params = null)
    {
        return $this->fetchAll($params)->toArray();
    }
    
    public function findAll($where = null, $orderBy = null, $limit = null, $offset = null)
	{
		return $this->fetchAll($where, $orderBy, $limit, $offset);
	}

	/**
	 * Count rows
	 * @param string|array $where
	 * @return int
	 */
	public function count($where = null)
	{
		$select = $this->select();
		$select->from($this->_name,'COUNT(*) AS num');
		$select = $this->_where($select, $where);
        return $this->fetchRow($select)->num;
	}
    
    /**
	 * This funcion should not be used. You CAN NOT rely on its results.
	 * @deprecated
	 * @return int or NULL
	 */
	public function getNextAutoIncrement()
	{
		try {
			$id = $this->getAdapter()->fetchOne(
			'SELECT AUTO_INCREMENT FROM information_schema.TABLES'
			. ' WHERE TABLE_NAME = ? AND TABLE_SCHEMA = ?',
			array($this->_name, $this->getSchemaName()));
		} catch(Exception $e) {
			return null;
		}
		return $id;
	}
    
    /**
	 * This function should not be used too often.
	 * @return string shema name
	 */
	public function getSchemaName()
	{
		$cfg = $this->getAdapter()->getConfig();
		$name = isset($cfg['dbname']) ? $cfg['dbname'] : null;
		if(! empty($name)){
			return $name;
		}else{
			return $this->getAdapter()->fetchOne("select DATABASE();");
		}
	}
    
    /**
	 * Set adapter to use
	 * @param Zend_Db_Adapter_Abstract $adapter
	 * @return Model_Website
	 */
	public function setAdapter(Zend_Db_Adapter_Abstract $adapter)
	{
		$this->adapter = $adapter;
		return $this;
	}
	
    /**
	 * Get front controller
	 * @deprecated replace it with getFrontController
	 * @return Zend_Controller_Front
	 */
	protected function getFront()
	{
		return Zend_Controller_Front::getInstance();
	}
	
	/**
	 * @return Zend_Controller_Front
	 */
	protected function getFrontController()
	{
		return Zend_Controller_Front::getInstance();
	}
	
	/**
	 * Get db adapter
	 * @return Zend_Db_Adapter_Abstract
	 */
	public function getAdapter()
	{
		if($this->adapter === null){
			$this->adapter = Zend_Db_Table::getDefaultAdapter();
		}
		return $this->adapter;
	}
	
	/**
	 * Get client's remote address (not checking proxy)
	 * @deprecated
	 * @return string ip address
	 */
	public function getClientRemoteAddress()
	{
		return $this->getFront()->getRequest()->getClientIp();
	}
	
	/**
	 * Get table
	 * @param string $name
	 * @return WZModel_DbTable_Abstract
	 */
	public static function getModel($name)
	{
		$parts = explode('_', $name);
		foreach ($parts as &$part)
			$part = ucfirst($part);
		$name = implode($parts);
		$class = 'WZModel_' . ucfirst($name);
		return new $class;
	}
    
    public function findParentRow($parentTable, $where = null, $order = null, $count = null, $offset = null, $ruleKey = null)
	{
		$cacheKey = md5(@serialize(func_get_args()));
		
		if( ($result = getvar($cacheKey)) === null ){
			if(! ($where instanceof Zend_Db_Table_Select)){
				$select = $this->select();
			} else {
				$select = $where;
			}
			
			if($where !== null){
				$this->_where($select, $where);
			}
			if($order !== null){
				$this->_order($select, $order);
			}
			if($count !== null || $offset !== null){
				$select->limit($count, $offset);
			}
			
			$result = parent::findParentRow($parentTable, $ruleKey, $select);
			
			setvar($cacheKey, $result);
		}
		return $result;
	}
	
	/**
	 * Find children in given $table that is in a child->parent relation with the current row.
	 *
     * @param string|Zend_Db_Table_Abstract  $dependentTable
     * @param string|array|Zend_Db_Table_Select $where  OPTIONAL An SQL WHERE clause or Zend_Db_Table_Select object.
     * @param string|array                      $order  OPTIONAL An SQL ORDER clause.
     * @param int                               $count  OPTIONAL An SQL LIMIT count.
     * @param int                               $offset OPTIONAL An SQL LIMIT offset.
	 * @param string                         OPTIONAL $ruleKey
     * @return Zend_Db_Table_Rowset_Abstract Query result from $dependentTable
     * @throws Zend_Db_Table_Row_Exception If $dependentTable is not a table or is not loadable.
     * @todo add cache
	 */
	public function findDependentRowset($dependentTable, $where = null, $order = null, $count = null, $offset = null, $ruleKey = null)
	{

		if(! ($where instanceof Zend_Db_Table_Select)){
			$select = $this->select();
		} else {
			$select = $where;
		}
		
		if($where !== null){
			$this->_where($select, $where);
		}
		if($order !== null){
			$this->_order($select, $order);
		}
		if($count !== null || $offset !== null){
			$select->limit($count, $offset);
		}
		
		$result = parent::findDependentRowset($dependentTable, $ruleKey, $select);

		return $result;
	}
	
	/**
	 * findMany2many.
	 *
     * @param  string|Zend_Db_Table_Abstract  $matchTable
     * @param  string|Zend_Db_Table_Abstract  $intersectionTable
     * @param  string                         OPTIONAL $callerRefRule
     * @param  string                         OPTIONAL $matchRefRule
     * @param  Zend_Db_Table_Select           OPTIONAL $select
     * @return Zend_Db_Table_Rowset_Abstract Query result from $matchTable
     * @throws Zend_Db_Table_Row_Exception If $matchTable or $intersectionTable is not a table class or is not loadable.
     */
	public function _findManyToManyRowset($matchTable, $intersectionTable, $callerRefRule = null, $matchRefRule = null, Zend_Db_Table_Select $select = null)
    {
    	$result = parent::findManyToManyRowset(
    		$matchTable,
    		$intersectionTable,
    		$callerRefRule,
    		$matchRefRule,
    		$select
        );

    	return $result;
	}
	
	/**
     * Fetches all rows using intersection table.
     *
     * @param string|array|Zend_Db_Table_Select $where  OPTIONAL An SQL WHERE clause or Zend_Db_Table_Select object.
     * @param string|array                      $order  OPTIONAL An SQL ORDER clause.
     * @param int                               $count  OPTIONAL An SQL LIMIT count.
     * @param int                               $offset OPTIONAL An SQL LIMIT offset.
 	 * @param  string|Zend_Db_Table_Abstract  	$matchTable
     * @param  string|Zend_Db_Table_Abstract  	$intersectionTable
     * @param  string                         	OPTIONAL $callerRefRule
     * @param  string                         	OPTIONAL $matchRefRule
     * @return Zend_Db_Table_Rowset_Abstract The row results per the Zend_Db_Adapter fetch mode.
     */
	public function findManyToManyRowset($where = null, $order = null, $count = null, $offset = null, $matchTable, $intersectionTable, $callerRefRule = null, $matchRefRule = null)
    {

		if(! ($where instanceof Zend_Db_Table_Select)){
			$select = $this->select();
		} else {
			$select = $where;
		}
		
		if($where !== null){
			$this->_where($select, $where);
		}
		if($order !== null){
			$this->_order($select, $order);
		}
		if($count !== null || $offset !== null){
			$select->limit($count, $offset);
		}
		
		$result = $this->_findManyToManyRowset
			($matchTable, $intersectionTable, $callerRefRule, $matchRefRule, $select);

		return $result;
	}
	
	/**
     * Generate WHERE clause from user-supplied string or array
     *
     * @param  string|array $where  OPTIONAL An SQL WHERE clause.
     * @return Zend_Db_Table_Select
     */
    protected function _where(Zend_Db_Table_Select $select, $where)
    {
        $where = (array) $where;
        foreach ($where as $key => $val) {
            // is $key an int?
            if (is_int($key)) {
                // $val is the full condition
                $select->where($val);
            } else {
                // $key is the condition with placeholder,
                // and $val is quoted into the condition
                $select->where($key, $val);
            }
        }
        return $select;
    }

    /**
     * Generate ORDER clause from user-supplied string or array
     *
     * @param  string|array $order  OPTIONAL An SQL ORDER clause.
     * @return Zend_Db_Table_Select
     */
    protected function _order(Zend_Db_Table_Select $select, $order)
    {
        if (!is_array($order)) {
            $order = array($order);
        }
        foreach ($order as $val) {
            $select->order($val);
        }
        return $select;
    }
}